package com.binary.coder.gift.core.domain.sys.impI;

import com.baomidou.mybatisplus.core.MybatisConfiguration;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.binary.coder.gift.core.common.bean.BizException;
import com.binary.coder.gift.core.common.bean.PageBean;
import com.binary.coder.gift.core.common.excel.ExcelExporter;
import com.binary.coder.gift.core.common.utils.IdMakerUtils;
import com.binary.coder.gift.core.common.utils.JacksonUtils;
import com.binary.coder.gift.core.common.utils.SysApiSqlUtils;
import com.binary.coder.gift.core.domain.sys.ISysApiSqlService;
import com.binary.coder.gift.core.infrastructure.mapper.sys.SysApiSqlMapper;
import com.binary.coder.gift.core.infrastructure.po.sys.SysApiSql;
import com.binary.coder.gift.core.interfaces.sys.dto.req.SysApiSqlAdd;
import com.binary.coder.gift.core.interfaces.sys.dto.req.SysApiSqlCellAliasUpdate;
import com.binary.coder.gift.core.interfaces.sys.dto.req.SysApiSqlQuery;
import com.binary.coder.gift.core.interfaces.sys.dto.req.SysApiSqlUpdate;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.defaults.DefaultSqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
@Service
public class SysApiSqlService extends ServiceImpl<SysApiSqlMapper, SysApiSql> implements ISysApiSqlService {

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Value("${api.debug:1}")
    private int apiDebug;

    @Override
    public Object queryDataById(long id, Map<String, Object> param) throws BizException {
        SysApiSql sysApiSql = baseMapper.selectById(id);
        if (sysApiSql == null) {
            throw new BizException("接口【" + id + "】不存在");
        }
        Object obj = this.query(sysApiSql, param);
        return obj;
    }

    @Override
    public Object queryDataByCode(String code, Map<String, Object> param) throws BizException {
        SysApiSql sysApiSql = this.getByCode(code);
        if (sysApiSql == null) {
            throw new BizException("接口【" + code + "】不存在");
        }
        Object obj = this.query(sysApiSql, param);
        return obj;
    }

    @Override
    public SysApiSql getByCode(String code) {
        return baseMapper.selectOneBean(baseMapper.createQuery().eq("code", code));
    }


    /**
     * 带缓存sql，直接返回sql执行结果
     *
     * @param api
     * @param param
     * @return
     */
    public List<Map<String, Object>> query(SysApiSql api, Map<String, Object> param) {
        return queryBySql(api.getId(), api.getSelectSql(), api.getScript(), api.getPage(), param);
    }

    @Override
    public List<Map<String, Object>> queryBySql(Long apiId, String sql, int script, int page, Map<String, Object> param) {
        String statementId = "sql." + apiId;
        if (apiDebug == 1) {
            // 每次唯一key
            statementId = "sql." + IdMakerUtils.getId("SysApiSqlService");
        }
        String sqlString = sql;
        log.info("ApiSql----- {}", sqlString);

        MybatisConfiguration configuration = (MybatisConfiguration) sqlSessionFactory.getConfiguration();

        // 构建statement
        makeStatement(configuration, statementId, script, sqlString, param);

        // 执行
        DefaultSqlSession defaultSqlSession = (DefaultSqlSession) sqlSessionFactory.openSession();
        try {
            List<Map<String, Object>> result = defaultSqlSession.selectList(statementId, param);
            return result;
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
            throw ex;
        } finally {
            defaultSqlSession.close();
            if (apiDebug == 1) {
                // 清除释放掉
                delCache(configuration, statementId);
                defaultSqlSession.close();
            }
        }
    }

    public void exportExcelByCode(ExcelExporter excelExporter, String code, Map<String, Object> param) throws BizException {
        SysApiSql sysApiSql = this.getByCode(code);
        if (sysApiSql == null) {
            throw new BizException("接口【" + code + "】不存在");
        }
        this.exportExcelBySql(excelExporter, sysApiSql.getId(), sysApiSql.getSelectSql(), sysApiSql.getScript(), param);
    }

    public void exportExcelBySql(ExcelExporter excelExporter, String selectSql, Map<String, Object> param, int script) {
        this.exportExcelBySql(excelExporter, IdMakerUtils.getId("SysApiSqlService"), selectSql, script, param);
    }

    @Override
    public void exportExcelBySql(ExcelExporter excelExporter, String selectSql, Map<String, Object> param) {
        int script = selectSql.indexOf("</") >= 0 ? 1 : 0;
        String sql = SysApiSqlUtils.tryEscape(script, selectSql);
        this.exportExcelBySql(excelExporter, IdMakerUtils.getId("SysApiSqlService"), sql, script, param);
    }

    @Override
    public void exportExcelBySql(ExcelExporter excelExporter, Long apiId, String sql, int script, Map<String, Object> param) {
        String statementId = "sql.page" + apiId;
        if (apiDebug == 1) {
            // 每次唯一key
            statementId = "sql.page." + IdMakerUtils.getId("SysApiSqlService");
        }
        // 添加分页sql
        String sqlString = SysApiSqlUtils.sqlRemoveChar(sql, ';') + " limit  #{inner_sys_page_start} , #{inner_sys_page_size} ";
        log.info("ApiSql-----" + sqlString);

        MybatisConfiguration configuration = (MybatisConfiguration) sqlSessionFactory.getConfiguration();


        // 构建statement
        makeStatement(configuration, statementId, script, sqlString, param);

        try {
            // 执行
            DefaultSqlSession defaultSqlSession = (DefaultSqlSession) sqlSessionFactory.openSession();
            String finalStatementId = statementId;
            excelExporter.writeExcelData((index, size) -> {
                param.put("inner_sys_page_start", index * size);
                param.put("inner_sys_page_size", size);
                return defaultSqlSession.selectList(finalStatementId, param);
            });

        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
            throw ex;
        } finally {
            if (apiDebug == 1) {
                // 清除释放掉
                delCache(configuration, statementId);
            }
        }
    }

    private void makeStatement(MybatisConfiguration configuration, String statementId, int script, String sqlString, Map param) {
        if (apiDebug == 1 || !configuration.hasStatement(statementId)) {
            synchronized (this) {
                if (apiDebug == 1 || !configuration.hasStatement(statementId)) {
                    if (script == 1) {
                        // 添加script
                        sqlString = "<script>" + sqlString + "</script>";
                    }
                    LanguageDriver xmlLanguageDriver = new XMLLanguageDriver();
                    SqlSource sqlSource = xmlLanguageDriver.createSqlSource(configuration, sqlString.trim(), param == null ? Map.class : param.getClass());
                    MappedStatement ms = new MappedStatement.Builder(configuration, statementId, sqlSource, SqlCommandType.SELECT)
                            .resultMaps(new ArrayList<ResultMap>() {
                                {
                                    add(new ResultMap.Builder(configuration, "defaultResultMap", Map.class, new ArrayList<>(0))
                                            .build());
                                }
                            }).build();
                    // 设置
                    configuration.addMappedStatement(ms);

                    // 打印sql含参数
                    BoundSql boundSql = sqlSource.getBoundSql(param);
                    log.info("BoundSql---\r\n" + boundSql.getSql());
                }
            }
        }
    }

    private void delCache(Long apiId) {
        String statementId = "sql." + apiId;
        MybatisConfiguration configuration = (MybatisConfiguration) sqlSessionFactory.getConfiguration();
        delCache(configuration, statementId);
    }

    private void delCache(MybatisConfiguration configuration, String statementId) {
        try {
            Field field = configuration.getClass().getDeclaredField("mappedStatements");
            field.setAccessible(true);
            Map<String, MappedStatement> mappedStatements = (Map<String, MappedStatement>) field.get(configuration);
            mappedStatements.remove(statementId);
        } catch (Exception e) {
            log.error("清除释放失败：" + e.getMessage());
        }
    }

    @Override
    public String validSql(SysApiSql api, Map<String, Object> param) {
        String sqlString = api.getSelectSql();
        log.info("ApiSql-----\r\n" + sqlString);

        MybatisConfiguration configuration = (MybatisConfiguration) sqlSessionFactory.getConfiguration();
        if (api.getScript() == 1) {
            // 添加script
            sqlString = "<script>" + sqlString + "</script>";
        }

        LanguageDriver xmlLanguageDriver = new XMLLanguageDriver();
        SqlSource sqlSource = xmlLanguageDriver.createSqlSource(configuration, sqlString.trim(), Map.class);
        if (api.getParam() != null) {
            Set<String> keys = param.keySet();
            for (String s : api.getParam().split(";")) {
                if (!keys.contains(s)) {
                    param.put(s, s);
                }
            }
        }
        // 打印sql含参数
        BoundSql boundSql = sqlSource.getBoundSql(param);
        log.info("BoundSql---\r\n" + boundSql.getSql());
        return boundSql.getSql();
    }

    @Override
    public SysApiSql addBean(SysApiSqlAdd param, int script) throws BizException {
        if (null != getByCode(param.getCode())) {
            throw new BizException(param.getCode() + ",编码已经被使用");
        }
        SysApiSql bean = new SysApiSql();
        bean.setId(IdMakerUtils.getId("SysApiSql"));
        bean.setCode(param.getCode());
        bean.setName(param.getName());
        bean.setType(param.getType());
        bean.setPage(param.getPage());
        bean.setScript(script);
        bean.setParam(SysApiSqlUtils.getSqlParam(param.getSelectSql()));
        bean.setCell(SysApiSqlUtils.getResultCell(param.getSelectSql()));
        bean.setCellAlias(param.getCellAlias());
        bean.setSelectSql(SysApiSqlUtils.tryEscape(script, param.getSelectSql()));
        bean.setRemark(param.getRemark());
        bean.setUpdateTime(LocalDateTime.now());
        baseMapper.insert(bean);
        return bean;
    }

    @Override
    public SysApiSql updateBean(SysApiSqlUpdate param, int script) throws BizException {
        SysApiSql bean = getById(param.getId());
        bean.setName(param.getName());
        bean.setType(param.getType());
        bean.setPage(param.getPage());
        bean.setScript(script);
        bean.setRemark(param.getRemark());
        bean.setUpdateTime(LocalDateTime.now());
        bean.setParam(SysApiSqlUtils.getSqlParam(param.getSelectSql()));
        bean.setCell(SysApiSqlUtils.getResultCell(param.getSelectSql()));
        bean.setCellAlias(param.getCellAlias());
        bean.setSelectSql(SysApiSqlUtils.tryEscape(script, param.getSelectSql()));
        baseMapper.updateById(bean);
        this.delCache(bean.getId());
        return bean;
    }

    @Override
    public void saveCellAlias(SysApiSqlCellAliasUpdate param) {
        String alias = "{}";
        if (param.getCellAlias() != null) {
            alias = JacksonUtils.toJSONString(param.getCellAlias());
        }
        baseMapper.updateCellAlias(param.getId(), alias);
    }

    /**
     * 分页查询
     *
     * @param param
     * @return
     */
    @Override
    public PageBean<SysApiSql> queryByPage(SysApiSqlQuery param) {
        QueryWrapper<SysApiSql> qw = baseMapper.createQuery();
        if (StringUtils.isNotBlank(param.getName())) {
            qw.lambda().like(SysApiSql::getName, param.getName());
        }

        if (StringUtils.isNotBlank(param.getCode())) {
            qw.lambda().like(SysApiSql::getCode, param.getCode());
        }

        if (StringUtils.isNotBlank(param.getType())) {
            qw.lambda().like(SysApiSql::getType, param.getType());
        }

        qw.orderByDesc("id");

        return baseMapper.selectPage(baseMapper.createBizPage(param), qw);
    }

    @Override
    public SysApiSql getById(Long id) {
        return getById(id);
    }


    /**
     * 获取分页数据
     *
     * @return
     */
    @Override
    public Map<String, List<Map<String, String>>> getTableAndColumns() {
        List<Map<String, String>> cList = baseMapper.selectColumns();
        Map<String, List<Map<String, String>>> td = cList.stream().collect(Collectors.groupingBy((a) -> a.get("table_name")));
        return td;
    }
}

